﻿using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public enum XAnimationEventName
{
    None = 0,
    Event,
    AttackStart,
    AttackEnd,
    AttackWarnStart,
    AttackWarnEnd,
    Sound,
    Effect,
    RenderSetting,
    ForwardStart,
    ForwardEnd,
    BackwardStart,
    BackwardEnd,
    JumpStart,
    JumpEnd,
}

public enum XAnimationEventType
{
    None,
    Hide,
    Show,
}

public enum XAnimationAttackType
{
    None,
}

public class XActorComponent : MonoBehaviour
{
    protected Animator animator;
    protected Dictionary<string, AnimationClip> overridedAnimation = new Dictionary<string, AnimationClip>();
 
    [System.Serializable]
    public class BoneNogPoint
    {
        public Transform head = null;
        public Transform hit = null;
        public Transform pelvis = null;
        public Transform center = null;
        public Transform leftElbow = null;
        public Transform rightElbow = null;
        public Transform leftHand = null;
        public Transform rightHand = null;
        public Transform leftKnee = null;
        public Transform rightKnee = null;
        public Transform leftFoot = null;
        public Transform rightFoot = null;
        public Transform leftWeapon = null;
        public Transform rightWeapon = null;
        public Transform leftEye = null;
        public Transform rightEye = null;
        public Transform reserve1 = null;
        public Transform reserve2 = null;
        public Transform reserve3 = null;

        public Transform leftshoulderArmor = null;
        public Transform rightshoulderArmor = null;
        public Transform leftUpperArm = null;
        public Transform rightUpperArm = null;
        public Transform leftForearm = null;
        public Transform rightForearm = null;
        public Transform spine = null;
        public Transform spine1 = null;
        public Transform spine2 = null;
        public Transform leftThigh = null;
        public Transform rightThigh = null;
        public Transform leftCalf = null;
        public Transform rightCalf = null;

        public Transform weaponShieldL = null;
        public Transform weaponShieldR = null;

        public Transform dummyHead = null;
        public Transform dummyFoot = null;
        public Transform dummyHandL = null;
        public Transform dummyHandR = null;
        public Transform dummySword = null;
        public Transform dummyBack = null;
    }

    public BoneNogPoint NogPoint;
    public Dictionary<XBoneNogType, Transform> attachmentNog = new Dictionary<XBoneNogType, Transform>();

    [Header("Actor ID")]
    public int cid = 0;
    public float radius = 2;
    
    [Header("Draw debug info")]
    public bool isDrawGL = false;

    /// <summary>
    /// 
    /// </summary>
    private void Awake()
    {
        isDrawGL = false;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="transList"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    private Transform FindChildTransform(Transform[] transList, string name)
    {
        foreach (Transform trans in transList)
        {
            if (trans.name == name)
            {
                return trans;
            }
        }

        return null;
    }

    /// <summary>
    /// 
    /// </summary>
    private void BoneTransformToNog()
    {
        if (!animator)
            animator = GetComponent<Animator>();

        NogPoint.head = animator.GetBoneTransform(HumanBodyBones.Head);
        NogPoint.hit = animator.GetBoneTransform(HumanBodyBones.Chest);
        NogPoint.pelvis = animator.GetBoneTransform(HumanBodyBones.Hips);
        NogPoint.leftElbow = animator.GetBoneTransform(HumanBodyBones.LeftLowerArm);
        NogPoint.leftHand = animator.GetBoneTransform(HumanBodyBones.LeftHand);
        NogPoint.rightElbow = animator.GetBoneTransform(HumanBodyBones.RightLowerArm);
        NogPoint.rightHand = animator.GetBoneTransform(HumanBodyBones.RightHand);
        NogPoint.leftKnee = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg);
        NogPoint.rightKnee = animator.GetBoneTransform(HumanBodyBones.RightLowerLeg);
        NogPoint.leftFoot = animator.GetBoneTransform(HumanBodyBones.LeftFoot);
        NogPoint.rightFoot = animator.GetBoneTransform(HumanBodyBones.RightFoot);
        NogPoint.leftEye = animator.GetBoneTransform(HumanBodyBones.LeftEye);
        NogPoint.rightEye = animator.GetBoneTransform(HumanBodyBones.RightEye);
        NogPoint.spine = animator.GetBoneTransform(HumanBodyBones.Spine);

        NogPoint.center = transform.Find(XBoneNogType.Renderer.ToString());
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="bindType"></param>
    /// <returns></returns>
    public Transform GetBoneNogPoint(XBoneNogType bindType)
    {
        Transform nogPointTrans = null;
        switch (bindType)
        {
            case XBoneNogType.Head:
                nogPointTrans = NogPoint.head;
                break;
            case XBoneNogType.Hit:
                nogPointTrans = NogPoint.hit;
                break;
            case XBoneNogType.Pelvis:
                nogPointTrans = NogPoint.pelvis;
                break;
            case XBoneNogType.Center:
                nogPointTrans = NogPoint.center;
                break;
            case XBoneNogType.LElbow:
                nogPointTrans = NogPoint.leftElbow;
                break;
            case XBoneNogType.RElbow:
                nogPointTrans = NogPoint.rightElbow;
                break;
            case XBoneNogType.LHand:
                nogPointTrans = NogPoint.leftHand;
                break;
            case XBoneNogType.RHand:
                nogPointTrans = NogPoint.rightHand;
                break;
            case XBoneNogType.LKnee:
                nogPointTrans = NogPoint.leftKnee;
                break;
            case XBoneNogType.RKnee:
                nogPointTrans = NogPoint.rightKnee;
                break;
            case XBoneNogType.LFoot:
                nogPointTrans = NogPoint.leftFoot;
                break;
            case XBoneNogType.RFoot:
                nogPointTrans = NogPoint.rightFoot;
                break;
            case XBoneNogType.LWeapon:
                nogPointTrans = NogPoint.leftWeapon;
                break;
            case XBoneNogType.RWeapon:
                nogPointTrans = NogPoint.rightWeapon;
                break;
            case XBoneNogType.Reserve1:
                nogPointTrans = NogPoint.reserve1;
                break;
            case XBoneNogType.Reserve2:
                nogPointTrans = NogPoint.reserve2;
                break;
            case XBoneNogType.Reserve3:
                nogPointTrans = NogPoint.reserve3;
                break;
            case XBoneNogType.LShoulderArmor:
                nogPointTrans = NogPoint.leftshoulderArmor;
                break;
            case XBoneNogType.RShoulderArmor:
                nogPointTrans = NogPoint.rightshoulderArmor;
                break;
            case XBoneNogType.LUpperArm:
                nogPointTrans = NogPoint.leftUpperArm;
                break;
            case XBoneNogType.RUpperArm:
                nogPointTrans = NogPoint.rightUpperArm;
                break;
            case XBoneNogType.LForearm:
                nogPointTrans = NogPoint.leftForearm;
                break;
            case XBoneNogType.RForearm:
                nogPointTrans = NogPoint.rightForearm;
                break;
            case XBoneNogType.Spine:
                nogPointTrans = NogPoint.spine;
                break;
            case XBoneNogType.Spine1:
                nogPointTrans = NogPoint.spine1;
                break;
            case XBoneNogType.Spine2:
                nogPointTrans = NogPoint.spine2;
                break;
            case XBoneNogType.LCalf:
                nogPointTrans = NogPoint.leftCalf;
                break;
            case XBoneNogType.RCalf:
                nogPointTrans = NogPoint.rightCalf;
                break;
            case XBoneNogType.WeaponShieldL:
                nogPointTrans = NogPoint.weaponShieldL;
                break;
            case XBoneNogType.WeaponShieldR:
                nogPointTrans = NogPoint.weaponShieldR;
                break;
            case XBoneNogType.DummyHead:
                nogPointTrans = NogPoint.dummyHead;
                break;
            case XBoneNogType.DummyFoot:
                nogPointTrans = NogPoint.dummyFoot;
                break;
            case XBoneNogType.DummyHandL:
                nogPointTrans = NogPoint.dummyHandL;
                break;
            case XBoneNogType.DummyHandR:
                nogPointTrans = NogPoint.dummyHandR;
                break;
            case XBoneNogType.DummySword:
                nogPointTrans = NogPoint.dummySword;
                break;
            case XBoneNogType.DummyBack:
                nogPointTrans = NogPoint.dummyBack;
                break;

            case XBoneNogType.WeaponMount:
                Transform rightTrans = NogPoint.weaponShieldR;
                if (rightTrans && rightTrans.childCount > 0)
                {
                    Transform weapon = rightTrans.GetChild(0);
                    if (weapon)
                    {
                        nogPointTrans = weapon.Find(XBoneNogType.WeaponMount.ToString());
                    }
                }
                break;

            default:
                break;
        }


        if (!nogPointTrans)
        {
            if (attachmentNog.ContainsKey(bindType))
            {
                nogPointTrans = attachmentNog[bindType];
            }
            else
            {
                nogPointTrans = transform;
            }
        }

        return nogPointTrans;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="nogType"></param>
    /// <param name="transform"></param>
    public void AddBoneNog(XBoneNogType nogType,  Transform transform)
    {
        if (!attachmentNog.ContainsKey(nogType))
        {
            attachmentNog.Add(nogType, transform);
        }
        else
        {
            attachmentNog[nogType] = transform;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="nogType"></param>
    public void RemoveBoneNog(XBoneNogType nogType)
    {
        if (attachmentNog.ContainsKey(nogType))
        {
            attachmentNog.Remove(nogType);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public enum FindAllNogName
    {
        weaponShield_l,
        weaponShield_r,
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="root"></param>
    private void FindAllNogPoint(Transform root)
    {
        foreach (Transform child in root)
        {
            string name = child.name.ToLower();
            if (name.Equals(FindAllNogName.weaponShield_l.ToString().ToLower()))
            {
                NogPoint.weaponShieldL = child;
            }
            else if (name.Equals(FindAllNogName.weaponShield_r.ToString().ToLower()))
            {
                NogPoint.weaponShieldR = child;
            }
            else if (name.Equals(XBoneNogType.DummyHead.ToString().ToLower()))
            {
                NogPoint.dummyHead = child;
            }
            else if (name.Equals(XBoneNogType.DummyFoot.ToString().ToLower()))
            {
                NogPoint.dummyFoot = child;
            }
            else if (name.Equals(XBoneNogType.DummyHandL.ToString().ToLower()))
            {
                NogPoint.dummyHandL = child;
            }
            else if (name.Equals(XBoneNogType.DummyHandR.ToString().ToLower()))
            {
                NogPoint.dummyHandR = child;
            }
            else if (name.Equals(XBoneNogType.DummySword.ToString().ToLower()))
            {
                NogPoint.dummySword = child;
            }
            else if (name.Equals(XBoneNogType.DummyBack.ToString().ToLower()))
            {
                NogPoint.dummyBack = child;
            }

            FindAllNogPoint(child);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    [ContextMenu("Generate Bone NogPoint")]
    public void GenerateBoneNogPoint()
    {
        NogPoint = new BoneNogPoint();
        BoneTransformToNog();

        FindAllNogPoint(transform);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public Vector3 GetMiddlePoint()
    {
        if (NogPoint.leftFoot && NogPoint.rightFoot && NogPoint.head)
        {
            return Vector3.Lerp(Vector3.Lerp(NogPoint.leftFoot.position, NogPoint.rightFoot.position, 0.5f),
                NogPoint.head.position, 0.5f);
        }

        return NogPoint.center.position;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="layer"></param>
    public void SetLayer(int layer)
    {
        foreach (Transform trans in GetComponentsInChildren<Transform>())
        {
            trans.gameObject.layer = layer;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="name"></param>
    /// <param name="clip"></param>
    public void SetAnimatorOverrideAnimation(string name, AnimationClip clip)
    {
        if (!animator)
            return;

        AnimatorOverrideController controller = animator.runtimeAnimatorController as AnimatorOverrideController;
        if (controller != null && clip != null)
        {
            AnimationClip old = controller[name];
            controller[name] = clip;

            if (!overridedAnimation.ContainsKey(name))
                overridedAnimation.Add(name, old);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="name"></param>
    /// <param name="clip"></param>
    public void SetAnimatorOverrideAnimation(string name, string clip)
    {
        if (!animator)
            return;

        AnimatorOverrideController controller = animator.runtimeAnimatorController as AnimatorOverrideController;
        if (controller != null && !string.IsNullOrEmpty(clip))
        {
            AnimationClip old = controller[name];
            controller[name] = controller[clip];

            if (!overridedAnimation.ContainsKey(name))
                overridedAnimation.Add(name, old);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="name"></param>
    public void RevertOverrideAnimation(string name)
    {
        if (!animator)
            return;

        AnimationClip clip;
        overridedAnimation.TryGetValue(name, out clip);

        if (clip != null)
        {
            AnimatorOverrideController controller = animator.runtimeAnimatorController as AnimatorOverrideController;
            if (controller != null && clip != null)
            {
                controller[name] = clip;
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public AnimatorStateInfo GetCurrentAnimatorState()
    {
        if (animator.IsInTransition(0))
            return animator.GetNextAnimatorStateInfo(0);

        return animator.GetCurrentAnimatorStateInfo(0);
    }

#if UNITY_EDITOR
    void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, radius);
    }
#endif

    enum XTraceMeshType
    {
        Sector,
    }

    public class XTraceMesh
    {
        public Material checkMeshMat;
        public Mesh checkMesh;
        public float duration;
    }

    private Dictionary<XTraceMeshType, XTraceMesh> 
        traceMeshs = new Dictionary<XTraceMeshType, XTraceMesh>();

    /// <summary>
    /// 
    /// </summary>
    private void Update()
    {
        if (isDrawGL)
        {
            DrawTraceMeshGL();
        }
    }

    /// <summary>
    /// 
    /// </summary>
    private void OnDestroy()
    {
        foreach(KeyValuePair<XTraceMeshType, XTraceMesh> it in traceMeshs)
        {
            Destroy(it.Value.checkMesh);
            Destroy(it.Value.checkMeshMat);
        }

        traceMeshs.Clear();
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="vertices"></param>
    /// <param name="material"></param>
    /// <returns></returns>
    private XTraceMesh CreateSectorMesh(List<Vector3> vertices, Material material)
    {
        Mesh mesh = new Mesh();
        
        int[] triangles;
        int triangleAmount = vertices.Count - 2;
        triangles = new int[3 * triangleAmount];

        for (int i = 0; i < triangleAmount; i++)
        {
            triangles[3 * i] = 0;
            triangles[3 * i + 1] = i + 1;
            triangles[3 * i + 2] = i + 2;
        }

        mesh.vertices = vertices.ToArray();
        mesh.triangles = triangles;

        Material meshMat;
        if (material)
        {
            meshMat = Instantiate<Material>(material);
        }
        else
        {
            meshMat = new Material(Shader.Find("Common/Effect/QuadColor"));
            meshMat.SetColor("_Color", new Color(1f, 0.92f, 0.016f, 0.4f));
        }

        XTraceMesh trace = new XTraceMesh() {
            checkMesh = mesh, checkMeshMat = meshMat
        };

        return trace;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="width"></param>
    /// <param name="distance"></param>
    /// <param name="material"></param>
    /// <param name="duration"></param>
    /// <returns></returns>
    public bool DrawSectorRect(float width, float distance, Material material, float duration)
    {
        if (!isDrawGL)
            return false;

        Vector3 forward = transform.forward;
        List<Vector3> vertices = new List<Vector3>();

        float halfWidth = width / 2f;

        Vector3 center = transform.localPosition;
        vertices.Add(-transform.right * halfWidth + center);
        vertices.Add(-transform.right * halfWidth + forward * distance + center);
        vertices.Add(transform.right * halfWidth + forward * distance + center);
        vertices.Add(transform.right * halfWidth + center);

        XTraceMesh mesh;
        if (!traceMeshs.ContainsKey(XTraceMeshType.Sector))
        {
            mesh = CreateSectorMesh(vertices, material);
            traceMeshs.Add(XTraceMeshType.Sector, mesh);
        }
        else
        {
            mesh = traceMeshs[XTraceMeshType.Sector];
            mesh.checkMesh.Clear();

            mesh.checkMesh.vertices = vertices.ToArray();
            mesh.checkMesh.triangles = new int[] { 0, 1, 2, 0, 2, 3 };
            mesh.duration = duration;
        }

        return true;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="t"></param>
    /// <param name="center"></param>
    /// <param name="angle"></param>
    /// <param name="radius"></param>
    public bool DrawSectorBegin(float angle, float radius, Material material, float duration)
    {
        if (!isDrawGL)
            return false;

        int pointAmmount = 100;
        float eachAngle = angle / pointAmmount;

        Vector3 forward = transform.forward;
        List<Vector3> vertices = new List<Vector3>();
        Vector3 center = transform.localPosition;

        vertices.Add(center);
        for (int i = 0; i < pointAmmount; i++)
        {
            Vector3 pos = Quaternion.Euler(0f, -angle / 2 + eachAngle * (i - 1), 0f) * forward * radius + center;
            vertices.Add(pos);
        }

        int[] triangles;
        int triangleAmount = vertices.Count - 2;
        triangles = new int[3 * triangleAmount];

        for (int i = 0; i < triangleAmount; i++)
        {
            triangles[3 * i] = 0;
            triangles[3 * i + 1] = i + 1;
            triangles[3 * i + 2] = i + 2;
        }

        XTraceMesh mesh;
        if (!traceMeshs.ContainsKey(XTraceMeshType.Sector))
        {
            mesh = CreateSectorMesh(vertices, material);
            traceMeshs.Add(XTraceMeshType.Sector, mesh);
        }
        else
        {
            mesh = traceMeshs[XTraceMeshType.Sector];
            mesh.checkMesh.Clear();

            mesh.checkMesh.vertices = vertices.ToArray();
            mesh.checkMesh.triangles = triangles;
            mesh.duration = duration;
        }

        return true;
    }

    /// <summary>
    /// 
    /// </summary>
    public void DrawSectorEnd()
    {
       if (traceMeshs.ContainsKey(XTraceMeshType.Sector))
        {
            XTraceMesh mesh = traceMeshs[XTraceMeshType.Sector];
            Destroy(mesh.checkMesh);
            Destroy(mesh.checkMeshMat);

            traceMeshs.Remove(XTraceMeshType.Sector);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public void DrawTraceMeshGL()
    {
        foreach (KeyValuePair<XTraceMeshType, XTraceMesh> mesh in traceMeshs)
        {
            Graphics.DrawMesh(mesh.Value.checkMesh, Vector3.zero, Quaternion.identity, mesh.Value.checkMeshMat, 0);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public XAnimationEventName GetFunctionType(string name)
    {
        XAnimationEventName type = XAnimationEventName.None;
        switch (name)
        {
            case "OnAnimationEvent":
                type = XAnimationEventName.Event;
                break;
            case "OnAnimationAttackStart":
                type = XAnimationEventName.AttackStart;
                break;
            case "OnAnimationAttackEnd":
                type = XAnimationEventName.AttackEnd;
                break;
            case "OnAnimationAttackWarnStart":
                type = XAnimationEventName.AttackWarnStart;
                break;
            case "OnAnimationAttackWarnEnd":
                type = XAnimationEventName.AttackWarnEnd;
                break;
            case "OnAnimationSound":
                type = XAnimationEventName.Sound;
                break;
            case "OnAnimationEffect":
                type = XAnimationEventName.Effect;
                break;
            case "OnAnimationForwardStart":
                type = XAnimationEventName.ForwardStart;
                break;
            case "OnAnimationForwardEnd":
                type = XAnimationEventName.ForwardEnd;
                break;
            case "OnAnimationBackwardStart":
                type = XAnimationEventName.BackwardStart;
                break;
            case "OnAnimationBackwardEnd":
                type = XAnimationEventName.BackwardEnd;
                break;
            case "OnAnimationJumpStart":
                type = XAnimationEventName.JumpStart;
                break;
            case "OnAnimationJumpEnd":
                type = XAnimationEventName.JumpEnd;
                break;
        }

        return type;
    }
}
